/*-
 * Copyright (c) 2010 mod_cfp team <mod-cfp@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * $Id: mod_cfp.c 173 2010-07-13 19:05:02Z mod.cfp $
 */

#include <assert.h>
#include <dlfcn.h>

#include "apr.h"
#include "apr_strings.h"
#include "apr_thread_proc.h"    /* for RLIMIT stuff */
#include "apr_optional.h"
#include "apr_buckets.h"
#include "apr_lib.h"
#include "apr_poll.h"

#define APR_WANT_STRFUNC
#define APR_WANT_MEMFUNC
#include "apr_want.h"

#define CORE_PRIVATE

#include "util_filter.h"
#include "ap_config.h"
#include "httpd.h"
#include "http_config.h"
#include "http_request.h"
#include "http_core.h"
#include "http_protocol.h"
#include "http_main.h"
#include "http_log.h"
#include "util_script.h"
#include "ap_mpm.h"
#include "mod_core.h"
#include "mod_cfp.h"

struct cfp_config {
	unsigned		magic;
#define	CFP_CONFIG_MAGIC	0x4c4c202c
	server_rec		*server;
	struct cfp_module	cm;
	void			*dlh;
	struct cfp_conf		cfp_conf;
};

struct cfp_priv {
	request_rec		*request;
	int			status;		/* HTTP response status */
};

module AP_MODULE_DECLARE_DATA cfp_module;

/* ------------------------------------------------------------------------- */

/*
 * Common routines
 */
void
cfp_log(struct cfp_module *cm, const char *fmt, ...)
{
	struct cfp_config *cc;
	va_list ap;
	char buf[8192];

	assert(cm->magic == CFP_MODULE_MAGIC);
	cc = cm->srv_arg;

	assert(fmt != NULL);
	va_start(ap, fmt);
	vsnprintf(buf, sizeof(buf), fmt, ap);
	ap_log_error(APLOG_MARK, APLOG_ERR, 0, cc->server, "%s", buf);
	va_end(ap);
}

int
cfp_fprintf(struct cfp_sess *sp, const char *fmt, ...)
{
	struct cfp_config *cc;
	struct cfp_module *cm;
	struct cfp_priv *cp;
	request_rec *r;
	va_list ap;
	int len;
	char buf[BUFSIZ];

	cp = sp->srv_arg;
	cm = sp->module;
	assert(cm->magic == CFP_MODULE_MAGIC);
	cc = cm->srv_arg;
	assert(cc->magic == CFP_CONFIG_MAGIC);
	r = cp->request;

	assert(fmt != NULL);
	va_start(ap, fmt);
	/* XXX calls vsnprintf(3) twice */
	len = vsnprintf(buf, BUFSIZ, fmt, ap);
	if (len >= BUFSIZ) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}
	va_end(ap);

	if (ap_rwrite(buf, len, r) < 0) {
		sp->errnum = CFP_ECONNRESET;
		return (-1);
	}

	return (len);
}

int
cfp_get_request_origuri(struct cfp_sess *sp, struct cfp_txt *txt)
{
	struct cfp_priv *cp;
	request_rec *r;

	if (txt == NULL) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}

	cp = sp->srv_arg;
	r = cp->request;

	txt->b = r->unparsed_uri;
	txt->e = r->unparsed_uri + strlen(r->unparsed_uri);
	return (0);
}

int
cfp_get_requrl(struct cfp_sess *sp, struct cfp_txt *txt)
{
	struct cfp_priv *cp;
	request_rec *r;

	if (txt == NULL) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}

	cp = sp->srv_arg;
	r = cp->request;

	txt->b = r->uri;
	txt->e = r->uri + strlen(r->uri);
	return (0);
}

int
cfp_get_querystring(struct cfp_sess *sp, struct cfp_txt *txt)
{
	struct cfp_priv *cp;
	request_rec *r;

	if (txt == NULL) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}

	cp = sp->srv_arg;
	r = cp->request;

	txt->b = r->args;
	txt->e = r->args + strlen(r->args);
	return (0);
}

int
cfp_get_request_method(struct cfp_sess *sp, struct cfp_txt *txt)
{
	struct cfp_priv *cp;
	request_rec *r;

	if (txt == NULL) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}

	cp = sp->srv_arg;
	r = cp->request;

	txt->b = r->method;
	txt->e = r->method + strlen(r->method);
	return (0);
}

int
cfp_get_query(struct cfp_sess *sp, const char *key, struct cfp_txt *txt)
{

	(void)sp;
	(void)key;
	(void)txt;
	/* XXX TODO */
	return (CFP_EINVAL);
}

int
cfp_get_postquery(struct cfp_sess *sp, const char *key, struct cfp_txt *txt)
{

	(void)sp;
	(void)key;
	(void)txt;
	/* XXX TODO */
	return (CFP_EINVAL);
}

int
cfp_getenv(struct cfp_sess *sp, const char *key, struct cfp_txt *txt)
{
	struct cfp_priv *cp;
	request_rec *r;
	const char *env;

	if (key == NULL || txt == NULL) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}

	cp = sp->srv_arg;
	r = cp->request;

	env = apr_table_get(r->subprocess_env, key);
	if (env == NULL) {
		sp->errnum = CFP_ENOENT;
		return (-1);
	}
	txt->b = env;
	txt->e = env + strlen(env);
	return (0);
}

int
cfp_get_header(struct cfp_sess *sp, const char *key, struct cfp_txt *txt)
{
	struct cfp_priv *cp;
	request_rec *r;
	const char *hdr;

	if (key == NULL || txt == NULL) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}

	cp = sp->srv_arg;
	r = cp->request;

	hdr = apr_table_get(r->headers_in, key);
	if (hdr == NULL) {
		sp->errnum = CFP_ENOENT;
		return (-1);
	}
	txt->b = hdr;
	txt->e = hdr + strlen(hdr);
	return (0);
}

int
cfp_set_status(struct cfp_sess *sp, int status)
{
	struct cfp_priv *cp;

	if (status < 100 && status >= 1000) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}

	cp = sp->srv_arg;
	cp->status = status;
	return (0);
}

int
cfp_set_header(struct cfp_sess *sp, const char *key, const char *value)
{
	struct cfp_priv *cp;
	request_rec *r;
	size_t keylen;

	if (sp == NULL || key == NULL || value == NULL) {
		sp->errnum = CFP_EINVAL;
		return (-1);
	}

	cp = sp->srv_arg;
	r = cp->request;

	keylen = strlen(key);
	switch (keylen) {
	case 12:
		if (strncasecmp(key, "Content-Type", keylen) == 0)
			ap_set_content_type(r, apr_pstrdup(r->pool, value));
		break;
	case 14:
		if (strncasecmp(key, "Content-Length", keylen) == 0) {
			long v;
			char *end;

			errno = 0;
			v = strtol(value, &end, 10);
			if (errno == ERANGE || value == end) {
				sp->errnum = CFP_EINVAL;
				return (-1);
			}
			ap_set_content_length(r, v);
		}
		break;
	default:
		apr_table_set(r->headers_out, key, value);
		break;
	}

	return (0);
}

/* ------------------------------------------------------------------------- */

static apr_status_t
modcfp_destroy_config(void *arg)
{
	struct cfp_config *cc = arg;

	if (cc == NULL)
		return (APR_SUCCESS);
	if (cc->cfp_conf.detach != NULL)
		cc->cfp_conf.detach(&cc->cm);
	if (cc->dlh != NULL) {
		dlclose(cc->dlh);
		cc->dlh = NULL;
	}
	return (APR_SUCCESS);
}

static void *
modcfp_create_config(apr_pool_t *p, server_rec *s)
{
	struct cfp_config *cc;

	cc = (struct cfp_config *)apr_pcalloc(p, sizeof(*cc));
	assert(cc != NULL);
	cc->magic = CFP_CONFIG_MAGIC;
	cc->server = s;
	cc->cm.magic = CFP_MODULE_MAGIC;
	cc->cm.srv_arg = cc;
	apr_pool_cleanup_register(p, cc, modcfp_destroy_config,
	    apr_pool_cleanup_null);
	return (cc);
}

static int
modcfp_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp,
    server_rec *s)
{
	void *data = NULL;
	const char *userdata_key = "ap2hook_cfp_post_config";

	(void)pconf;
	(void)plog;
	(void)ptemp;

	/*
	 * Apache will load, unload and then reload a DSO module. This
	 * prevents us from starting PHP until the second load.
	*/
	apr_pool_userdata_get(&data, userdata_key, s->process->pool);
	if (data == NULL) {
		/*
		 * We must use set() here and *not* setn(), otherwise the
		 * static string pointed to by userdata_key will be mapped
		 * to a different location when the DSO is reloaded and the
		 * pointers won't match, causing get() to return NULL when
		 * we expected it to return non-NULL.
		 */
		apr_pool_userdata_set((const void *)1, userdata_key,
		    apr_pool_cleanup_null, s->process->pool);
		return (OK);
	}
	return (OK);
}

static void
modcfp_handle_aborted_connection(request_rec *r)
{

	/* XXX DELETE ME (just for debugging) */
	ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "aborted connection");
}

static struct cfp_sess *
mod_cfp_sessnew(request_rec *r, struct cfp_config *cc)
{
	struct cfp_priv *cp;
	struct cfp_sess *sp;

	sp = apr_pcalloc(r->pool, sizeof(*sp) + sizeof(*cp));
	assert(sp != NULL);
	cp = (struct cfp_priv *)(sp + 1);
	cp->request = r;

	sp->module = &cc->cm;
	sp->conf = &cc->cfp_conf;
	sp->srv_arg = cp;
	return (sp);
}

static void
modcfp_checkfd(request_rec *r, struct cfp_sess *sp, int want)
{
	apr_int32_t nfds;
	apr_os_sock_info_t sock_info;
	apr_pollfd_t pfds[1];
	apr_socket_t *sd;
	apr_status_t status;

	/* XXX only TCP is supported */
	sock_info.os_sock = &(sp->fd);
	sock_info.local = NULL;
	sock_info.remote = NULL;
	sock_info.family = APR_INET;
	sock_info.type = SOCK_STREAM;

	status = apr_os_sock_make(&sd, &sock_info, r->pool);
	assert(status == APR_SUCCESS);

	pfds[0].reqevents = (want == CFP_RET_SOCKET_READABLE) ?
	    APR_POLLIN : APR_POLLOUT;
	pfds[0].desc_type = APR_POLL_SOCKET;
	pfds[0].desc.s = sd;

	do {
		status = apr_poll(&pfds[0], 1, &nfds, -1);
	} while (APR_STATUS_IS_EINTR(status));

	/* no timeout */
	assert(APR_STATUS_IS_TIMEUP(status) == 0);
	/* XXX why eagain? */
	assert(APR_STATUS_IS_EAGAIN(status) == 0);
}

static int
modcfp_handler(request_rec *r)
{
	struct cfp_conf *cnf;
	struct cfp_config *cc;
	struct cfp_priv *cp;
	struct cfp_sess *sp;
	apr_bucket *bucket;
	apr_bucket_brigade *brigade;
	apr_status_t rv;
	int ret;

	cc = ap_get_module_config(r->server->module_config, &cfp_module);
	if (cc == NULL)
		return (DECLINED);
	if (r->main == NULL || r->subprocess_env != r->main->subprocess_env) {
		ap_add_common_vars(r);
		ap_add_cgi_vars(r);
	}

	cnf = &cc->cfp_conf;
	/*
	 * XXX I think we need to seperate mod_cfp_sessnew() into two parts
	 * for efficiency that some variables could have reasons when
	 * lookup was successful.
	 */
	sp = mod_cfp_sessnew(r, cc);
	assert(sp != NULL);
	cp = (struct cfp_priv *)sp->srv_arg;

	if (cnf->lookup == NULL)
		return (DECLINED);
	ret = cnf->lookup(sp);
	if (ret != CFP_MATCH_OK)
		return (DECLINED);
	if (cnf->init != NULL) {
		/* XXX currently no checking the return value */
		(void)cnf->init(sp);
	}

	brigade = apr_brigade_create(r->pool, r->connection->bucket_alloc);
	assert(brigade != NULL);

	do {
		ret = cnf->start(sp);
		switch (ret) {
		case CFP_RET_SOCKET_READABLE:
		case CFP_RET_SOCKET_WRITABLE:
			modcfp_checkfd(r, sp, ret);
			break;
		case CFP_RET_DISK_READ:
		case CFP_RET_DISK_WRITE:
			/* TODO unsupported asynchrous disk I/O */
			break;
		case CFP_RET_DONE:
		case CFP_RET_ERROR:
		default:
			break;
		}
	} while (ret == CFP_RET_SOCKET_READABLE ||
	    ret == CFP_RET_SOCKET_WRITABLE);

	bucket = apr_bucket_eos_create(r->connection->bucket_alloc);
	APR_BRIGADE_INSERT_TAIL(brigade, bucket);

	rv = ap_pass_brigade(r->output_filters, brigade);
	if (rv != APR_SUCCESS || r->connection->aborted)
		modcfp_handle_aborted_connection(r);
	apr_brigade_cleanup(brigade);

	if (cnf->fini != NULL)
		cnf->fini(sp);

	return ((cp->status == 0) ? DECLINED : cp->status);
}

static const char *
modcfp_set_module(cmd_parms *cmd, void *dummy, const char *arg)
{
	struct cfp_conf const *cnf;
	struct cfp_config *cc;
	server_rec *s;

	(void)dummy;
	(void)arg;

	s = cmd->server;
	if (s == NULL)
		return ("server pointer is NULL");
	cc = ap_get_module_config(s->module_config, &cfp_module);
	if (cc == NULL)
		return ("struct cfp_config * is NULL");
	cc->dlh = dlopen(arg, RTLD_NOW | RTLD_LOCAL);
	if (cc->dlh == NULL) {
		ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
		    "dlopen(3) error: %s", dlerror());
		return ("dlopen(3) error");
	}
	cnf = dlsym(cc->dlh, "conf");
	if (cnf == NULL) {
		ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
		    "dlsym(3) error: %s", dlerror());
		return ("dlsym(3) error");
	}
	assert(cnf->magic == CFP_CONF_MAGIC);
	memcpy(&cc->cfp_conf, cnf, sizeof(*cnf));
	if (cc->cfp_conf.attach != NULL)
		cc->cfp_conf.attach(&cc->cm);
	return (NULL);
}

static const command_rec modcfp_commands[] = {
	AP_INIT_TAKE1("CFPModule", modcfp_set_module, NULL, RSRC_CONF,
	    "The shared library name for mod_cfp")
};

static void
modcfp_register_hook(apr_pool_t *p)
{

	(void)p;
	ap_hook_post_config(modcfp_post_config, NULL, NULL, APR_HOOK_MIDDLE);
	ap_hook_handler(modcfp_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA cfp_module =
{
	STANDARD20_MODULE_STUFF,
	NULL,			/* dir config creater */
	NULL,			/* dir merger --- default is to override */
	modcfp_create_config,	/* server config */
	NULL,			/* merge server config */
	modcfp_commands,	/* command apr_table_t */
	modcfp_register_hook	/* register hooks */
};
